GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

CountUp   B
last analyzed

Complexity

Total Complexity 45

Size/Duplication

Total Lines 258
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 208
dl 0
loc 258
rs 8.8
c 0
b 0
f 0
wmc 45

12 Functions

Rating   Name   Duplication   Size   Complexity  
A ensureNumber 0 3 1
A resetDuration 0 5 1
C count 0 43 11
A printValue 0 11 3
A update 0 15 3
A validateValue 0 8 2
C determineDirectionAndSmartEasing 0 19 10
A start 0 13 3
A reset 0 9 1
A pauseResume 0 13 2
A easeOutExpo 0 2 1
B formatNumber 0 28 7

How to fix   Complexity   

Complexity

Complex classes like CountUp often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
export interface CountUpOptions { // (default)
2
  startVal?: number; // number to start at (0)
3
  decimalPlaces?: number; // number of decimal places (0)
4
  duration?: number; // animation duration in seconds (2)
5
  useGrouping?: boolean; // example: 1,000 vs 1000 (true)
6
  useEasing?: boolean; // ease animation (true)
7
  smartEasingThreshold?: number; // smooth easing for large numbers above this if useEasing (999)
8
  smartEasingAmount?: number; // amount to be eased for numbers above threshold (333)
9
  separator?: string; // grouping separator (,)
10
  decimal?: string; // decimal (.)
11
  // easingFn: easing function for animation (easeOutExpo)
12
  easingFn?: (t: number, b: number, c: number, d: number) => number;
13
  formattingFn?: (n: number) => string; // this function formats result
14
  prefix?: string; // text prepended to result
15
  suffix?: string; // text appended to result
16
  numerals?: string[]; // numeral glyph substitution
17
}
18
19
// playground: stackblitz.com/edit/countup-typescript
20
export class CountUp {
21
22
  version = '2.0.4';
23
  private defaults: CountUpOptions = {
24
    startVal: 0,
25
    decimalPlaces: 0,
26
    duration: 2,
27
    useEasing: true,
28
    useGrouping: true,
29
    smartEasingThreshold: 999,
30
    smartEasingAmount: 333,
31
    separator: ',',
32
    decimal: '.',
33
    prefix: '',
34
    suffix: ''
35
  };
36
  private el: HTMLElement | HTMLInputElement;
37
  private rAF: any;
38
  private startTime: number;
39
  private decimalMult: number;
40
  private remaining: number;
41
  private finalEndVal: number = null; // for smart easing
42
  private useEasing = true;
43
  private countDown = false;
44
  formattingFn: (num: number) => string;
45
  easingFn?: (t: number, b: number, c: number, d: number) => number;
46
  callback: (args?: any) => any;
47
  error = '';
48
  startVal = 0;
49
  duration: number;
50
  paused = true;
51
  frameVal: number;
52
53
  constructor(
54
    private target: string | HTMLElement | HTMLInputElement,
55
    private endVal: number,
56
    private options?: CountUpOptions
57
  ) {
58
    this.options = {
59
      ...this.defaults,
60
      ...options
61
    };
62
    this.formattingFn = (this.options.formattingFn) ?
63
      this.options.formattingFn : this.formatNumber;
64
    this.easingFn = (this.options.easingFn) ?
65
      this.options.easingFn : this.easeOutExpo;
66
67
    this.startVal = this.validateValue(this.options.startVal);
68
    this.frameVal = this.startVal;
69
    this.endVal = this.validateValue(endVal);
70
    this.options.decimalPlaces = Math.max(0 || this.options.decimalPlaces);
71
    this.decimalMult = Math.pow(10, this.options.decimalPlaces);
72
    this.resetDuration();
73
    this.options.separator = String(this.options.separator);
74
    this.useEasing = this.options.useEasing;
75
    if (this.options.separator === '') {
76
      this.options.useGrouping = false;
77
    }
78
    this.el = (typeof target === 'string') ? document.getElementById(target) : target;
79
    if (this.el) {
80
      this.printValue(this.startVal);
81
    } else {
82
      this.error = '[CountUp] target is null or undefined';
83
    }
84
  }
85
86
  // determines where easing starts and whether to count down or up
87
  private determineDirectionAndSmartEasing() {
88
    const end = (this.finalEndVal) ? this.finalEndVal : this.endVal;
89
    this.countDown = (this.startVal > end);
90
    const animateAmount = end - this.startVal;
91
    if (Math.abs(animateAmount) > this.options.smartEasingThreshold) {
92
      this.finalEndVal = end;
93
      const up = (this.countDown) ? 1 : -1;
94
      this.endVal = end + (up * this.options.smartEasingAmount);
95
      this.duration = this.duration / 2;
96
    } else {
97
      this.endVal = end;
98
      this.finalEndVal = null;
99
    }
100
    if (this.finalEndVal) {
101
      this.useEasing = false;
102
    } else {
103
      this.useEasing = this.options.useEasing;
104
    }
105
  }
106
107
  // start animation
108
  start(callback?: (args?: any) => any) {
109
    if (this.error) {
110
      return;
111
    }
112
    this.callback = callback;
113
    if (this.duration > 0) {
114
      this.determineDirectionAndSmartEasing();
115
      this.paused = false;
116
      this.rAF = requestAnimationFrame(this.count);
117
    } else {
118
      this.printValue(this.endVal);
119
    }
120
  }
121
122
  // pause/resume animation
123
  pauseResume() {
124
    if (!this.paused) {
125
      cancelAnimationFrame(this.rAF);
126
    } else {
127
      this.startTime = null;
128
      this.duration = this.remaining;
129
      this.startVal = this.frameVal;
130
      this.determineDirectionAndSmartEasing();
131
      this.rAF = requestAnimationFrame(this.count);
132
    }
133
    this.paused = !this.paused;
134
  }
135
136
  // reset to startVal so animation can be run again
137
  reset() {
138
    cancelAnimationFrame(this.rAF);
139
    this.paused = true;
140
    this.resetDuration();
141
    this.startVal = this.validateValue(this.options.startVal);
142
    this.frameVal = this.startVal;
143
    this.printValue(this.startVal);
144
  }
145
146
  // pass a new endVal and start animation
147
  update(newEndVal) {
148
    cancelAnimationFrame(this.rAF);
149
    this.startTime = null;
150
    this.endVal = this.validateValue(newEndVal);
151
    if (this.endVal === this.frameVal) {
152
      return;
153
    }
154
    this.startVal = this.frameVal;
155
    if (!this.finalEndVal) {
156
      this.resetDuration();
157
    }
158
    this.determineDirectionAndSmartEasing();
159
    this.rAF = requestAnimationFrame(this.count);
160
  }
161
162
  count = (timestamp: number) => {
163
    if (!this.startTime) { this.startTime = timestamp; }
164
165
    const progress = timestamp - this.startTime;
166
    this.remaining = this.duration - progress;
167
168
    // to ease or not to ease
169
    if (this.useEasing) {
170
      if (this.countDown) {
171
        this.frameVal = this.startVal - this.easingFn(progress, 0, this.startVal - this.endVal, this.duration);
172
      } else {
173
        this.frameVal = this.easingFn(progress, this.startVal, this.endVal - this.startVal, this.duration);
174
      }
175
    } else {
176
      if (this.countDown) {
177
        this.frameVal = this.startVal - ((this.startVal - this.endVal) * (progress / this.duration));
178
      } else {
179
        this.frameVal = this.startVal + (this.endVal - this.startVal) * (progress / this.duration);
180
      }
181
    }
182
183
    // don't go past endVal since progress can exceed duration in the last frame
184
    if (this.countDown) {
185
      this.frameVal = (this.frameVal < this.endVal) ? this.endVal : this.frameVal;
186
    } else {
187
      this.frameVal = (this.frameVal > this.endVal) ? this.endVal : this.frameVal;
188
    }
189
190
    // decimal
191
    this.frameVal = Math.round(this.frameVal * this.decimalMult) / this.decimalMult;
192
193
    // format and print value
194
    this.printValue(this.frameVal);
195
196
    // whether to continue
197
    if (progress < this.duration) {
198
      this.rAF = requestAnimationFrame(this.count);
199
    } else if (this.finalEndVal !== null) {
200
      // smart easing
201
      this.update(this.finalEndVal);
202
    } else {
203
      if (this.callback) {
204
        this.callback();
205
      }
206
    }
207
  }
208
209
  printValue(val: number) {
210
    const result = this.formattingFn(val);
211
212
    if (this.el.tagName === 'INPUT') {
213
      const input = this.el as HTMLInputElement;
214
      input.value = result;
215
    } else if (this.el.tagName === 'text' || this.el.tagName === 'tspan') {
216
      this.el.textContent = result;
217
    } else {
218
      this.el.innerHTML = result;
219
    }
220
  }
221
222
  ensureNumber(n: any) {
223
    return (typeof n === 'number' && !isNaN(n));
224
  }
225
226
  validateValue(value: number): number {
227
    const newValue = Number(value);
228
    if (!this.ensureNumber(newValue)) {
229
      this.error = `[CountUp] invalid start or end value: ${value}`;
230
      return null;
231
    } else {
232
      return newValue;
233
    }
234
  }
235
236
  private resetDuration() {
237
    this.startTime = null;
238
    this.duration = Number(this.options.duration) * 1000;
239
    this.remaining = this.duration;
240
  }
241
242
  // default format and easing functions
243
244
  formatNumber = (num: number): string => {
245
    const neg = (num < 0) ? '-' : '';
246
    let result: string,
247
      x: string[],
248
      x1: string,
249
      x2: string,
250
      x3: string;
251
    result = Math.abs(num).toFixed(this.options.decimalPlaces);
252
    result += '';
253
    x = result.split('.');
254
    x1 = x[0];
255
    x2 = x.length > 1 ? this.options.decimal + x[1] : '';
256
    if (this.options.useGrouping) {
257
      x3 = '';
258
      for (let i = 0, len = x1.length; i < len; ++i) {
259
        if (i !== 0 && (i % 3) === 0) {
260
          x3 = this.options.separator + x3;
261
        }
262
        x3 = x1[len - i - 1] + x3;
263
      }
264
      x1 = x3;
265
    }
266
    // optional numeral substitution
267
    if (this.options.numerals && this.options.numerals.length) {
268
      x1 = x1.replace(/[0-9]/g, (w) => this.options.numerals[+w]);
269
      x2 = x2.replace(/[0-9]/g, (w) => this.options.numerals[+w]);
270
    }
271
    return neg + this.options.prefix + x1 + x2 + this.options.suffix;
272
  }
273
274
  easeOutExpo = (t: number, b: number, c: number, d: number): number =>
275
    c * (-Math.pow(2, -10 * t / d) + 1) * 1024 / 1023 + b
276
277
}
278